package org.budo.support.servlet.util;

import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.PrintWriter;
import java.nio.ByteBuffer;
import java.nio.channels.SeekableByteChannel;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.nio.file.StandardOpenOption;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import javax.servlet.ServletOutputStream;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletResponse;

import org.budo.support.lang.util.FileUtil;
import org.budo.support.lang.util.IoUtil;
import org.budo.support.mvcs.Mvcs;

import lombok.extern.slf4j.Slf4j;

/**
 * @author limingwei
 * @date 2016年6月7日 上午9:46:40
 */
@Slf4j
public class ResponseUtil {
    private static final int BUFFER_LENGTH = 1024 * 16;

    private static final Pattern RANGE_PATTERN = Pattern.compile("bytes=(?<start>\\d*)-(?<end>\\d*)");

    public static void mediaStream(HttpServletResponse response, String filePath, String encodedFileName, String range) {
        if (null == filePath || !FileUtil.exists(filePath)) {
            log.error("#38 mediaStream return, file not exists, filePath=" + filePath);
            sendError(response, 404, filePath + " not exists");
            return;
        }

        Path video = Paths.get(filePath);

        long length = (long) FileUtil.fileSize(video);
        long start = 0;
        long end = length - 1;

        range = range == null ? "" : range;
        Matcher matcher = RANGE_PATTERN.matcher(range);

        if (matcher.matches()) {
            String startGroup = matcher.group("start");
            start = startGroup.isEmpty() ? start : Long.valueOf(startGroup);
            start = start < 0 ? 0 : start;

            String endGroup = matcher.group("end");
            end = endGroup.isEmpty() ? end : Long.valueOf(endGroup);
            end = end > length - 1 ? length - 1 : end;
        }

        long contentLen = end - start + 1;

        response.reset();
        response.setBufferSize(BUFFER_LENGTH);

        response.setContentType(FileUtil.probeContentType(video));
        response.setDateHeader("Last-Modified", FileUtil.getLastModifiedTime(video).toMillis());

        String contentDisposition = String.format("inline;filename=\"%s\"", encodedFileName);
        response.setHeader("Content-Disposition", contentDisposition);
        response.setHeader("Accept-Ranges", "bytes");

        String contentRange = String.format("bytes %s-%s/%s", start, end, length);
        response.setHeader("Content-Range", contentRange);

        String contentLength = String.format("%s", contentLen);
        response.setHeader("Content-Length", contentLength);
        response.setStatus(HttpServletResponse.SC_PARTIAL_CONTENT);

        long readLen;
        long leftLen = contentLen;
        ByteBuffer byteBuffer = ByteBuffer.allocate(BUFFER_LENGTH);

        SeekableByteChannel seekableByteChannel = FileUtil.newByteChannel(video, StandardOpenOption.READ); //
        OutputStream outputStream = Mvcs.getOutputStream(response);

        try {
            seekableByteChannel.position(start);

            while ((readLen = seekableByteChannel.read(byteBuffer)) != -1 && leftLen > 0) {
                byteBuffer.clear();
                long len = leftLen < readLen ? leftLen : readLen;

                outputStream.write(byteBuffer.array(), 0, (int) len);
                leftLen -= readLen;
            }
        } catch (Throwable e) {
            String error = e + "";
            if (error.contains("你的主机中的软件中止了一个已建立的连接。") || error.contains("EofException")) {
                log.warn("#132 " + e + ", " + e.getCause());
            } else {
                throw new RuntimeException(e);
            }
        } finally {
            IoUtil.close(seekableByteChannel);
            IoUtil.close(outputStream);
        }
    }

    public static PrintWriter getWriter(HttpServletResponse response) {
        try {
            return response.getWriter();
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
    }

    public static ServletOutputStream getOutputStream(ServletResponse response) {
        try {
            return response.getOutputStream();
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
    }

    public static ServletOutputStream getCaptchaOutputStream(HttpServletResponse response) {
        // 禁止图像缓存
        response.setDateHeader("Expires", 0);
        response.setHeader("Pragma", "no-cache");
        response.setContentType("image/jpeg");
        response.setHeader("Cache-Control", "no-store, no-cache, must-revalidate");

        return getOutputStream(response);
    }

    public static void flushBuffer(ServletResponse response) {
        try {
            response.flushBuffer();
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
    }

    public static void sendRedirect(HttpServletResponse response, String location) {
        try {
            response.sendRedirect(location);
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
    }

    public static void writeToResponse(ServletResponse response, String text) {
        InputStream inputStream = new ByteArrayInputStream(text.getBytes());
        IoUtil.write(inputStream, ResponseUtil.getOutputStream(response));
    }

    public static void writeToResponse(ServletResponse response, byte[] bytes) {
        InputStream inputStream = new ByteArrayInputStream(bytes);
        IoUtil.write(inputStream, ResponseUtil.getOutputStream(response));
    }

    public static void writeToResponse(String text) {
        writeToResponse(Mvcs.getResponse(), text);
    }

    public static void sendError(HttpServletResponse response, Integer statusCode, String descriptiveMessage) {
        try {
            response.sendError(statusCode, descriptiveMessage);
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
    }

    public static void sendError(HttpServletResponse response, int statusCode) {
        try {
            response.sendError(statusCode);
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
    }
}